package com.liaoyifan.core.util;

import com.liaoyifan.core.model.KeyPairEncoded;
import com.liaoyifan.core.valid.Assert;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.IESParameterSpec;
import org.bouncycastle.util.encoders.Hex;

/** 加密工具类，提供多种加密算法支持 */
@SuppressWarnings("unused")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Secret {
    // 算法常量
    private static final String EC = "EC";
    private static final String ECDSA = "SHA256withECDSA";
    private static final String ECIES = "ECIES";
    private static final String AES = "AES";
    private static final String AES_CBC_PADDING = "AES/CBC/PKCS5Padding";
    private static final String HMAC_SHA256 = "HmacSHA256";
    private static final String CURVE_NAME = "secp256r1";
    private static final String PROVIDER_BC = "BC";

    // 加密参数
    private static final byte[] DERIVATION = "secret_derivation".getBytes(StandardCharsets.UTF_8);
    private static final byte[] ENCODING = "secret_encoding".getBytes(StandardCharsets.UTF_8);
    private static final int AES_KEY_SIZE = 256;
    private static final int MAC_KEY_SIZE = 256;
    private static final byte[] NONCE = Hex.decode("0001020304050607");

    // Base64编解码器
    private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
    private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();

    private static final SecureRandom SECURE_RANDOM = new SecureRandom();

    private static final int SIGN = 2;
    private static final int VERIFY = 3;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static String encryptECIES(String data, String publicKey) throws SecurityException {
        return processCipher(Cipher.ENCRYPT_MODE, data, publicKey, null);
    }

    public static String decryptECIES(String data, String privateKey) throws SecurityException {
        return processCipher(Cipher.DECRYPT_MODE, data, null, privateKey);
    }

    public static String signMAC(String data, String macKey) throws SecurityException {
        return processMac(data, macKey);
    }

    public static boolean verifyMAC(String data, String signature, String macKey)
            throws SecurityException {
        return signMAC(data, macKey).equals(signature);
    }

    public static SecretKey getMacKey(String macKey) {
        Assert.notBlank(macKey, "macKey cannot be blank");
        byte[] keyBytes = macKey.getBytes(StandardCharsets.UTF_8);
        if (keyBytes.length < 32) {
            throw new IllegalArgumentException("MAC密钥长度至少需要32字节");
        }
        return new SecretKeySpec(keyBytes, HMAC_SHA256);
    }

    public static String createMacKey() {
        return BASE64_ENCODER.encodeToString(SecureRandom.getSeed(32));
    }

    public static String encryptAES(String plainText, String aesKey) throws SecurityException {
        Assert.notBlank(plainText, "plainText cannot be blank");
        Assert.notBlank(aesKey, "aesKey cannot be blank");
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
            byte[] iv = new byte[cipher.getBlockSize()];
            SECURE_RANDOM.nextBytes(iv);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.ENCRYPT_MODE, getAESKey(aesKey), ivSpec);

            byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return BASE64_ENCODER.encodeToString(
                    ByteBuffer.allocate(iv.length + encrypted.length)
                            .put(iv)
                            .put(encrypted)
                            .array());
        } catch (Exception e) {
            throw new SecurityException("AES加密失败", e);
        }
    }

    public static String decryptAES(String cipherText, String aesKey) throws SecurityException {
        Assert.notBlank(cipherText, "cipherText cannot be blank");
        Assert.notBlank(aesKey, "aesKey cannot be blank");
        try {
            byte[] combined = BASE64_DECODER.decode(cipherText);
            int blockSize = 16; // AES块大小固定为16字节
            IvParameterSpec ivSpec = new IvParameterSpec(combined, 0, blockSize);
            byte[] encrypted = new byte[combined.length - blockSize];
            System.arraycopy(combined, blockSize, encrypted, 0, encrypted.length);

            Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, getAESKey(aesKey), ivSpec);
            return new String(cipher.doFinal(encrypted), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new SecurityException("AES解密失败", e);
        }
    }

    public static SecretKey getAESKey(String aesKey) {
        Assert.notBlank(aesKey, "aesKey cannot be blank");
        byte[] keyBytes = BASE64_DECODER.decode(aesKey);
        if (keyBytes.length != 16 && keyBytes.length != 24 && keyBytes.length != 32) {
            throw new IllegalArgumentException("AES密钥长度必须为16、24或32字节");
        }
        return new SecretKeySpec(keyBytes, AES);
    }

    public static String createAESKey() throws SecurityException {
        try {
            KeyGenerator keyGen = KeyGenerator.getInstance(AES);
            keyGen.init(AES_KEY_SIZE);
            return BASE64_ENCODER.encodeToString(keyGen.generateKey().getEncoded());
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException("生成AES密钥失败", e);
        }
    }

    public static String signECDSA(String data, String privateKey) throws SecurityException {
        return processSignature(SIGN, data, privateKey);
    }

    public static boolean verifyECDSA(String data, String signature, String publicKey)
            throws SecurityException {
        return processSignature(VERIFY, data, publicKey).equals(signature);
    }

    public static PrivateKey getPrivateKey(String privateKey) throws SecurityException {
        Assert.notBlank(privateKey, "privateKey cannot be blank");
        try {
            return KeyFactory.getInstance(EC)
                    .generatePrivate(new PKCS8EncodedKeySpec(BASE64_DECODER.decode(privateKey)));
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new SecurityException("获取私钥失败", e);
        }
    }

    public static PublicKey getPublicKey(String publicKey) throws SecurityException {
        Assert.notBlank(publicKey, "privateKey cannot be blank");
        try {
            return KeyFactory.getInstance(EC)
                    .generatePublic(new X509EncodedKeySpec(BASE64_DECODER.decode(publicKey)));
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new SecurityException("获取公钥失败", e);
        }
    }

    public static KeyPair createKeyPair() throws SecurityException {
        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance(EC);
            keyGen.initialize(new ECGenParameterSpec(CURVE_NAME), SecureRandom.getInstanceStrong());
            return keyGen.generateKeyPair();
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
            throw new SecurityException("生成密钥对失败", e);
        }
    }

    public static KeyPairEncoded createKeyPairEncoded() {
        KeyPair keyPair = createKeyPair();
        return new KeyPairEncoded(
                BASE64_ENCODER.encodeToString(keyPair.getPrivate().getEncoded()),
                BASE64_ENCODER.encodeToString(keyPair.getPublic().getEncoded()));
    }

    private static String processCipher(int mode, String data, String publicKey, String privateKey)
            throws SecurityException {
        Assert.notBlank(data, "data cannot be blank");
        Assert.notBlank(publicKey, "publicKey cannot be blank");
        Assert.notBlank(privateKey, "privateKey cannot be blank");
        try {
            Cipher cipher = Cipher.getInstance(Secret.ECIES, PROVIDER_BC);
            IESParameterSpec params =
                    new IESParameterSpec(DERIVATION, ENCODING, MAC_KEY_SIZE, AES_KEY_SIZE, NONCE);

            if (mode == Cipher.ENCRYPT_MODE) {
                cipher.init(
                        mode, getPublicKey(publicKey), params, SecureRandom.getInstanceStrong());
                return BASE64_ENCODER.encodeToString(
                        cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)));
            } else {
                cipher.init(
                        mode, getPrivateKey(privateKey), params, SecureRandom.getInstanceStrong());
                return new String(
                        cipher.doFinal(BASE64_DECODER.decode(data)), StandardCharsets.UTF_8);
            }
        } catch (Exception e) {
            throw new SecurityException(
                    Secret.ECIES + (mode == Cipher.ENCRYPT_MODE ? "加密" : "解密") + "失败", e);
        }
    }

    private static String processMac(String data, String macKey) throws SecurityException {
        Assert.notBlank(data, "data cannot be blank");
        Assert.notBlank(macKey, "macKey cannot be blank");
        try {
            Mac mac = Mac.getInstance(HMAC_SHA256);
            mac.init(getMacKey(macKey));
            return BASE64_ENCODER.encodeToString(
                    mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new SecurityException("HMAC签名失败", e);
        }
    }

    private static String processSignature(int mode, String data, String key)
            throws SecurityException {
        Assert.notBlank(data, "data cannot be blank");
        Assert.notBlank(key, "key cannot be blank");
        try {
            Signature signature = Signature.getInstance(Secret.ECDSA);
            if (mode == SIGN) {
                signature.initSign(getPrivateKey(key));
                signature.update(data.getBytes(StandardCharsets.UTF_8));
                return BASE64_ENCODER.encodeToString(signature.sign());
            } else {
                signature.initVerify(getPublicKey(key));
                signature.update(data.getBytes(StandardCharsets.UTF_8));
                return String.valueOf(signature.verify(BASE64_DECODER.decode(key)));
            }
        } catch (Exception e) {
            throw new SecurityException(Secret.ECDSA + (mode == SIGN ? "签名" : "验证") + "失败", e);
        }
    }

    /** PKCE codeVerifier */
    public static String getCodeVerifier() {
        byte[] codeVerifier = new byte[64];
        SECURE_RANDOM.nextBytes(codeVerifier);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(codeVerifier);
    }

    /** PKCE codeChallenge */
    public static String getCodeChallenge(String codeVerifier, String method) {
        if (!"S256".equals(method)) {
            throw new IllegalArgumentException("Unsupported code challenge method: " + method);
        }
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(codeVerifier.getBytes(StandardCharsets.UTF_8));
            return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("SHA-256 not supported", e);
        }
    }
}
